package IO流;

import org.junit.Test;
import java.io.*;

/*
    对象流
         ObjectInputStream和 ObjectOutputSteam
         用于存储和读取基本数据类型数据或对象的处理流,它的强大之处就是可以把Java中的对象写入到数据源中,也能把对象从数据源中还原回来
         序列化:用ObjectOutputStream类保存基本类型数据或对象的机制
         反序列化:用ObjectInputStream类读取基本类型数据或对象的机制
         ObjectOutputStream和 ObjectInputStream不能序列化static和transient修饰的成员变量,因为static代表该属性只归类所有而不是归对象所有,transient关键字则指明该属性不允许进行序列化,如果设置了两个关键字中的任意一个,则在序列化时会进行忽略,即值不会进行保存

  对象的序列化
       对象序列化机制允许把内存中的Java对象转换成平台无关的二进制流,从而允许把这种二进制流持久地保存在磁盘上,或通过网络将这种二进制流传输到另一个网络节点,当其它程序获取了这种二进制流,就可以恢复成原来的Java对象
       序列化的好处在于可将任何实现了Serializable接口的对象转化为字节数据,使其在保存和传输时可被还原
       序列化是 RMI(Remote Method Invoke – 远程方法调用)过程的参数和返回值都必须实现的机制,而 RMI是 JavaEE的基础,因此序列化机制是 JavaEE平台的基础
       如果需要让某个对象支持序列化机制,则必须让对象所属的类及其属性是可序列化的,为了让某个类是可序列化的,该类必须实现Serializable接口,否则会抛出NotSerializableException异常

      凡是实现Serializable接口的类都有一个表示序列化版本标识符的静态变量:
          private static final long serialVersionUID;
          serialVersionUID用来表明类的不同版本间的兼容性,简言之,其目的是以序列化对象进行版本控制,有关各版本反序列化时是否兼容
          如果类没有显示定义这个静态常量,它的值是Java运行时环境根据类的内部细节自动生成的,若类的实例变量做了修改,serialVersionUID可能发生变化,所以建议显式的进行声明
      简单来说,Java的序列化机制是通过在运行时判断类的serialVersionUID来验证版本一致性的,在进行反序列化时,JVM会把传来的字节流中的 serialVersionUID与本地相应实体类的 serialVersionUID进行比较,如果相同就认为是一致的,可以进行反序列化,否则就会出现序列化版本不一致的异常:(InvalidCastException)

    使用对象流序列化对象
         若某个类实现了 Serializable接口,则该类的对象就是可序列化的
             创建一个 ObjectOutputStream
             调用 ObjectOutputStream对象的 writeObject(对象)方法输出可序列化对象
             注意写出一次,操作flush()一次
         反序列化
             创建一个 ObjectInputStream
             调用 readObject()方法读取流中的对象
         如果某个类的属性不是基本数据类型或 String类型,而是另一个引用类型,那么这个引用类型必须是可序列化的,否则拥有该类型的 Field的类也不能序列化

    Serializable接口的理解
         实现了Serializable接口的对象,可将它们转换成一系列字节,并可在以后完全恢复回原来的样子,这一过程亦可通过网络进行,这意味着序列化机制能自动补偿操作系统间的差异,换句话说,可以先在Windows机器上创建一个对象,对其序列化,然后通过网络发给一台Unix机器,然后在那里准确无误地重新"装配",不必关心数据在不同机器上如何表示,也不必关心字节的顺序或者其他任何细节
         由于大部分作为参数的类如String、Integer等都实现了 java.io.Serializable的接口,也可以利用多态的性质,作为参数使接口更灵活
 */
public class IO08 {

    //序列化过程:将内存中的Java对象保存到磁盘中或通过网络传输出去
    @Test
    public void test1(){
        ObjectOutputStream objectOutputStream = null;

        try {
            objectOutputStream = new ObjectOutputStream(new FileOutputStream(new File("E:\\FileTest\\object.txt")));

            objectOutputStream.writeObject(new String("养天地正气,法古今完人"));
            objectOutputStream.flush();

            objectOutputStream.writeObject(new Person("胡明聪",25));
            objectOutputStream.flush();

            objectOutputStream.writeObject(new Person("小明",23,new Account(5000)));
            objectOutputStream.flush();

        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            if(objectOutputStream != null){
                try {
                    objectOutputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    //反序列化过程:将磁盘文件中的对象还原为内存中的一个java对象
    @Test
    public void test2(){
        ObjectInputStream objectInputStream = null;

        try {
            objectInputStream = new ObjectInputStream(new FileInputStream(new File("E:\\FileTest\\object.txt")));

            //读取的顺序需要和写入的顺序一致
            Object str = objectInputStream.readObject();
            Person person = (Person) objectInputStream.readObject();

            Person personAccount = (Person) objectInputStream.readObject();

            System.out.println((String)str);
            System.out.println(person.toString());
            System.out.println(personAccount.toString());

        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } finally {
            if(objectInputStream != null){
                try {
                    objectInputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

/*
    将该Person类持久化到文件中,需要满足如下要求
        1、需要实现接口:Serializable
        2、当前类需要提供一个全局常量:serialVersionUID,值没有具体要求,可自行指定
        3、除了当前Person类需要实现Serializable接口外,还必须保证其内部所有属性必须是可序列化的(默认情况下,基本数据类型是可序列化的)
 */
class Person implements Serializable{ //必须实现该接口,否则无法序列化

    public static final long serialVersionUID = 23423234L;

    private String name;
    private int age;
    private Account account;//保证其内部所有属性必须是可序列化的

    public Person() {
    }

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public Person(String name, int age, Account account) {
        this.name = name;
        this.age = age;
        this.account = account;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public Account getAccount() {
        return account;
    }

    public void setAccount(Account account) {
        this.account = account;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", account=" + account +
                '}';
    }
}

class Account implements Serializable{

    public static final long serialVersionUID = 433423234L;

    private double balance;

    public Account() {
    }

    public Account(double balance) {
        this.balance = balance;
    }

    public double getBalance() {
        return balance;
    }

    public void setBalance(double balance) {
        this.balance = balance;
    }

    @Override
    public String toString() {
        return "Account{" +
                "balance=" + balance +
                '}';
    }
}
